package com.example.buffer;

import org.apache.commons.io.FileUtils;
import org.junit.jupiter.api.Test;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

/**
 * @author 86130-leon
 * @date 2024/1/10--14:54
 * ByteBuffer主要有两种读写模式：写模式和读模式。它们之间的状态转换如下：
 * 初始状态：写模式
 * 调用flip()方法后：读模式
 * 调用clear()方法后：写模式
 * 调用compact()方法后：写模式
 * 在写模式下，我们可以使用put()方法往缓冲区中写入数据，使用get()、getChar()、getInt()等方法读取数据会抛出BufferUnderflowException异常。
 * 在读模式下，我们可以使用get()、getChar()、getInt()等方法读取缓冲区中的数据，使用put()方法往缓冲区中写入数据也会抛出BufferOverflowException异常。
 * <p>
 * <p>
 * Buffer 缓冲
 * coding训练：中文处理和词频计算：
 * 中文处理：UTF8编码的字符串(100个) + 缓冲大小=10 -->导致中文乱码
 * 词频计算-->1G个单词统计: 多少个线程？ + 如何并行运算
 */
public class BufferExamples {


    /**
     * 生成指定格式(ababa)的大文件
     *
     * @throws IOException
     */
    @Test
    public void generate_large_file() throws IOException {
        Random r = new Random();
        var fileName = "word";
        var bufferSize = 4 * 1024;
//        var fout=new FileOutputStream(fileName);//每次只读一个字节/字符，效率低
        var fout = new BufferedOutputStream(new FileOutputStream(fileName), bufferSize); //使用缓冲区，提升速度
        var start = System.currentTimeMillis();
        for (int i = 0; i < 1_000; i++) {
            // 生成长度为5内容为： abcde的随机字符串
            for (int j = 0; j < 5; j++) {
                fout.write(97 + r.nextInt(5));
            }
            fout.write(' ');
        }
        fout.close();
        System.out.format("程序执行时间: %d ms", (System.currentTimeMillis() - start));
    }

    /**
     * 读文件时间比较
     *
     * @throws IOException
     */
    @Test
    public void read_test() throws IOException {
        var fileName = "word";
        var in = new FileInputStream(fileName);
        var start = System.currentTimeMillis();
        int b;
        while ((b = in.read()) != -1) {
            //模拟读文件
        }
        var end = System.currentTimeMillis();
        System.out.format("耗时: %d ms", (end - start));
        in.close();
    }

    /**
     * 读文件时间比较: 使用缓冲区
     *
     * @throws IOException
     */
    @Test
    public void read_test_withBuffer() throws IOException {
        var fileName = "word";
        var in = new BufferedInputStream(new FileInputStream(fileName));
        var start = System.currentTimeMillis();
        int b;
        var bytes = new byte[8 * 1024]; //每次读取的大小  read() 默认一次只读取一个
        while ((b = in.read(bytes)) != -1) {

        }
        var end = System.currentTimeMillis();
        System.out.println("耗时: " + (end - start) + "ms");
        in.close();
    }


    /**
     * 使用nio读文件时间比较
     *
     * @throws IOException
     */
    @Test
    public void read_test_nio() throws IOException {
        var fileName = "word";
        var channel = new FileInputStream(fileName).getChannel();
        var buff = ByteBuffer.allocate(1024 * 8);//缓冲区大小
        var start = System.currentTimeMillis();
        while (channel.read(buff) != -1) {
            buff.flip();//读模式
            //读取数据
//            System.out.println(new String(buff.array()));
//            break;  //只读一行数据
            buff.clear();//写模式
        }
        System.out.format("%d ms\n", (System.currentTimeMillis() - start));
    }


    /**
     * 异步读取文件
     * 读取文件位置[start,end]的部分
     * @throws IOException
     * @throws ExecutionException
     * @throws InterruptedException
     */
    @Test
    public void test_async_read() throws IOException, ExecutionException, InterruptedException {
        var fileName = "word";
        File file = new File(fileName);
        System.out.format("文件大小: %d\t", FileUtils.sizeOf(file));
        System.out.format("文件大小: %d\t", file.length());
        System.out.format("文件大小: %d\t\n", new FileInputStream(fileName).available());

        var channel= AsynchronousFileChannel.open(Path.of(fileName), StandardOpenOption.READ);
        var buf = ByteBuffer.allocate(1024);
        Future<Integer> operation =channel.read(buf,0);// channel.read()非阻塞同步 从指定位置position开始读取文件
        var numReads = operation.get();//operation.get() 阻塞同步
        buf.flip();//读模式
        var chars = new String(buf.slice().array());
        System.out.println(chars);

    }

    /**
     * Buffer中文处理示例
     */
    @Test
    public void test_chinese() {
        var raw = "一二三，四五六，七八九";
        var charset = StandardCharsets.UTF_8;
        var bytes = charset.encode(raw).array(); // utf-8格式的 byte[]
        var bytes2 = Arrays.copyOfRange(bytes, 0, 11);//范围 [from,to)
        System.out.format("原始数组长度: %d ,新数组长度: %d \n",bytes.length,bytes2.length);
        System.out.format("原数组对应字符串: %s \n新数组对应字符串: %s \n",new String(bytes),new String(bytes2));

        var bbuf = ByteBuffer.allocate(11);
        var cbuf = CharBuffer.allocate(11);

        bbuf.put(bytes2);//写模式
        bbuf.flip();//读模式

        charset.newDecoder().decode(bbuf, cbuf, true);//  bbuf会将不足一个汉字的部分忽略
        cbuf.flip();//读模式

        var tmp = new char[cbuf.length()];
        // 缓冲区还存在元素
        if (cbuf.hasRemaining()) {
            cbuf.get(tmp);//将缓冲区内容复制到数组 tmp 中
            System.out.println("here: " + new String(tmp));
        }
        System.out.format("limit - pos = %d ,大于0说明还有内容被忽略了", bbuf.limit() - bbuf.position());
    }


}
